log4j与logback包冲突原因及解决,不可忽视的Warning 您所在的位置:网站首页 slf4j-log4j12 依赖冲突 log4j与logback包冲突原因及解决,不可忽视的Warning

log4j与logback包冲突原因及解决,不可忽视的Warning

2023-05-31 22:54| 来源: 网络整理| 查看: 265

场景

一个简单的spring-boot程序,需要用kafka做消息队列,于是在maven中引入kafka依赖,一切看似没问题,在启动时,打印出Warning信息:

SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. 原因分析

通过警告消息,可以简单的看出是slf4j绑定发生问题,有多个StaticLoggerBinder.class存在,即slf4j-log4j12和logback-classic冲突。

疑惑点1是我并没有手动引入slf4j-log4j12依赖,依赖jar包是被自动引入的,通过maven自带工具分析依赖路径,可以看出是kafka依赖于slf4j-log4j12,自动导入的依赖包。 日志绑定的机制分析 从日志对象开始探究slf4j的绑定方式。 import org.slf4j.Logger; import org.slf4j.LoggerFactory; ··· private final Logger logs = LoggerFactory.getLogger(***.class);

LoggerFactory.getLogger()方法:(下述均只保留关键逻辑代码 )

public static Logger getLogger(Class clazz) { Logger logger = getLogger(clazz.getName()); ··· return logger; } public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory();//看这里 return iLoggerFactory.getLogger(name);//根据名字返回一个Logger实例对象 }

ILoggerFactory是一个接口,归属package org.slf4j;仅存在一个方法为: public Logger getLogger(String name); 接下来就是看看getILoggerFactory()的真面目:

public static ILoggerFactory getILoggerFactory() { if (INITIALIZATION_STATE == UNINITIALIZED) { synchronized (LoggerFactory.class) { if (INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization();//看这里 } } } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: return StaticLoggerBinder.getSingleton().getLoggerFactory(); case NOP_FALLBACK_INITIALIZATION: return NOP_FALLBACK_FACTORY; case FAILED_INITIALIZATION: throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); case ONGOING_INITIALIZATION: // support re-entrant behavior. // See also http://jira.qos.ch/browse/SLF4J-97 return SUBST_FACTORY; } throw new IllegalStateException("Unreachable code"); }

可以看到performInitialization()是进行初始化的方法:

private final static void performInitialization() { bind(); ··· }

performInitialization()内部调用bind()方法:

private final static void bind() { try { Set staticLoggerBinderPathSet = null; if (!isAndroid()) { staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();//看这里 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); } // the next line does the binding StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(staticLoggerBinderPathSet); fixSubstituteLoggers(); replayEvents(); // release all resources in SUBST_FACTORY SUBST_FACTORY.clear(); } catch (NoClassDefFoundError ncde) {... } catch (java.lang.NoSuchMethodError nsme) {... } catch (Exception e) {...} }

其中关键在于findPossibleStaticLoggerBinderPathSet()方法,终于到了查找绑定相关的部分内容,可以看到是查找所有的"org/slf4j/impl/StaticLoggerBinder.class"类并加载,同时while循环里,将可能存在的多个StaticLoggerBinder.class路径均加入Set返回。

private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; static Set findPossibleStaticLoggerBinderPathSet() { Set staticLoggerBinderPathSet = new LinkedHashSet(); try { ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); Enumeration paths; if (loggerFactoryClassLoader == null) {//看这里 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); } else { paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); } while (paths.hasMoreElements()) {//看这里 URL path = paths.nextElement(); staticLoggerBinderPathSet.add(path); } } catch (IOException ioe) { Util.report("Error getting resources from path", ioe); } return staticLoggerBinderPathSet; }

返回到bind()方法中:

private final static void bind() { ··· staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);//看这里 ··· } ··· private static void reportMultipleBindingAmbiguity(Set binderPathSet) { if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { Util.report("Class path contains multiple SLF4J bindings."); for (URL path : binderPathSet) { Util.report("Found binding in [" + path + "]"); } Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); } }

这里可以看到reportMultipleBindingAmbiguity()里判断是否发生多重绑定,就是打印文章开头Warning信息的地方。 成功加载StaticLoggerBinder后,在bind()方法中调用其getSingleton()方法得到单例,并修改INITIALIZATION_STATE 状态,至此完成日志框架的绑定。

private final static void bind() { if (!isAndroid()) { staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); } StaticLoggerBinder.getSingleton();//看这里 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(staticLoggerBinderPathSet); ··· }

最后附上slf4j-api-1.7.25.jar和logback-classic-1.2.3.jar的目录结构供参考:

解决方案

分析了原因,那么解决方案自然很简单,就是剔除不需要的依赖包,此处就是在kafka的依赖中剔除slf4j-log4j12。maven项目中可以通过exclusions标签来完成。

org.apache.kafka kafka_2.11 0.10.0.1 org.slf4j slf4j-log4j12

本文就简单分析了日志加载绑定的过程,如有遗漏请不吝指出。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有